package cn.tannn.tconfig.server;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 分布式锁 - 选主
 * <p> 项目启动的时候会执行 init 会去抢锁，如果抢不到锁 locked 则就是false(从）反之就是主
 * <p> 查询mysql的`innodb_lock_wait_timeout/秒` `show variables like '@wait%'`
 *
 * @author <a href="https://tannn.cn/">tnnn</a>
 * @version V1.0
 * @date 2024/5/12 下午9:01
 */
@Component
@Slf4j
public class DistributedLocks {

    /**
     * 数据源
     */
    private final DataSource dataSource;
    Connection connection;
    /**
     * 锁的状态位
     * true: 主
     * false: 从
     */
    @Getter
    @Setter
    private AtomicBoolean locked = new AtomicBoolean(false);

    /**
     * 探主
     */
    final ScheduledExecutorService executor =  Executors.newScheduledThreadPool(1);


    public DistributedLocks(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 手动处理事务和锁
     */
    @PostConstruct
    public void  init(){
        try {
            this.connection = dataSource.getConnection();
        }catch (Exception e){
           log.error("init get connection error", e);
        }
        // 5s探活一次主
        // scheduleWithFixedDelay 属性值变化监听 - 上一个结束下一个才执行
        executor.scheduleWithFixedDelay(this::tryLock,1000, 5000, TimeUnit.MILLISECONDS);
    }


    /**
     * 获取锁
     */
    public boolean lock() throws SQLException {

        // 可控的手动进行 锁时长设置后执行在当前锁的时间内操作sql去竞争锁
        // 此操作不会污染数据库里的连接池连接，且可控
        connection.setAutoCommit(false);
        connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        connection.createStatement().execute("set innodb_lock_wait_timeout = 5");
        // 这个执行会被 lock 5s
        connection.createStatement().execute("select app from locks where id = 1 for update");
        // 这里没有 做 commit 就是为了占用这个连接

        if(locked.get()){
            // 不变
            log.debug(" ====> reenter this dist lock.");
        }else {
            // 晋升
            log.debug(" ====> get a dist lock.");
        }
        return true;
    }

    /**
     * 加锁
     */
    private void tryLock() {
        try {
            lock();
            locked.set(true);
        }catch (Exception e){
            log.error("lock failed .....");
            locked.set(false);
        }
    }


    /**
     * 项目下线关闭拿到的connection
     */
    @PreDestroy
    public void close(){
       try {
           if(connection != null && !connection.isClosed()){
               connection.rollback();
               connection.close();
           }
       }catch (Exception e){
           log.error("ignore exception");
       }
    }
}
